#include "stdafx.h"
#include "ChessTree.h"

#include "ChessFactory.h"
#include "ChessStatus.h"
#include "ChessTrainer.h"
#include "WinRate.h"

#include <LogWriter.h>
#include <DecisionTree.h>

using namespace ExperientialSystem;

ChessTree::ChessTree(WinnerChecker* winnerChecker):_tree(new DecisionTree(make_shared<ChessFactory>(winnerChecker)))
{
}

ChessTree::ChessTree(const std::string treePath, WinnerChecker* winnerChecker):
	_tree(new ExperientialSystem::DecisionTree(make_shared<ChessFactory>(winnerChecker)))
{
	ifstream in(treePath);
	_tree->Read(in);
	in.close();
	LOG_FORMAT("Read tree: %s", treePath.c_str());
}

ChessTree::~ChessTree()
{
}

void ChessTree::UpdateValue(shared_ptr<ExperientialSystem::IStatus> status)
{
	shared_ptr<ChessStatus> chessStatus = dynamic_pointer_cast<ChessStatus>(status);
	if (NULL != chessStatus)
	{
		TryAddNextStatus(chessStatus, 0);

		_tree->UpdateStatus(chessStatus.get());
	}
}

Point ChessTree::NextPoint(shared_ptr<ExperientialSystem::IStatus> status)
{
	shared_ptr<ChessStatus> curChessStatus = dynamic_pointer_cast<ChessStatus>(status);

	auto optimal = ComputeOptimalStatus(status);
	if (optimal == NULL)
	{
		return curChessStatus->GetAllEmptys().front();
	}

	ChessStatus* optimalChessStatus = dynamic_cast<ChessStatus*>(optimal);
	//Compute difference between current and next.
	//There is only one different piece.
	vector<Point> increBlack, increWhite, decreBlack, decreWhite;
	optimalChessStatus->Compare(curChessStatus.get(), increBlack, increWhite, decreBlack, decreWhite);

	assert(decreBlack.empty() && decreWhite.empty() && (increBlack.size() == 1 || increWhite.size() == 1));

	if (increBlack.size() == 1)
	{
		return increBlack.front();
	}
	else
	{
		return increWhite.front();
	}
}

void ChessTree::StartTraining()
{
	ChessTrainer trainer(_tree,this);
	trainer.SetEpochInterval(300);
	trainer.SetFinalOutput("tree.txt");
	trainer.Run();
}

void ChessTree::TryAddNextStatus(shared_ptr<ChessStatus> status, int depth)
{
	if (depth >= ADD_STATUS_DEPTH)
	{
		return;
	}

	if (!_tree->HasNextStatus(status.get()))
	{
		//Put next piece to all possible blanks and generate the next status.
		auto allEmpty = status->GetAllEmptys();
		auto allBlacks = status->GetAllBlacks();
		auto allWhites = status->GetAllWhites();
		bool blackToGo = status->ToGo() == ChessStatus::Black ? true : false;
		vector<shared_ptr<IStatus>> nextStatusList;
		for (auto empty : allEmpty)
		{
			if (blackToGo)
			{
				auto newBlacks = allBlacks;
				newBlacks.push_back(empty);
				shared_ptr<ChessStatus> nextStatus = make_shared<ChessStatus>(newBlacks, allWhites, ChessStatus::White);
				nextStatusList.push_back(nextStatus);
				TryAddNextStatus(nextStatus, ++depth);
			}
			else
			{
				auto newWhites = allWhites;
				newWhites.push_back(empty);
				shared_ptr<ChessStatus> nextStatus = make_shared<ChessStatus>(allBlacks, newWhites, ChessStatus::Black);
				nextStatusList.push_back(nextStatus);
				TryAddNextStatus(nextStatus, ++depth);
			}
		}
		_tree->MakeConnection(status, nextStatusList);
	}
}

ChessStatus* ChessTree::ComputeOptimalStatus(shared_ptr<ExperientialSystem::IStatus> status)
{
	auto statusPrt = _tree->GetStatus(status.get());
	if (statusPrt == NULL)
	{
		return NULL;
	}

	ChessStatus* chessStatus = dynamic_cast<ChessStatus*>(statusPrt);
	//Compute optimal next step.
	//There may be many step with same win rate ,then choose randomly.
	double maxWinrate = -1;
	vector<ChessStatus*> optStatus;
	for (auto next : chessStatus->GetAllNextStatus())
	{
		ChessStatus* nextStatus = dynamic_cast<ChessStatus*>(next);
		double winRate;
		chessStatus->GetWinRate(nextStatus, winRate);

		double nextOptValue;
		if (ComputeOptimalValue(nextStatus, 1, nextOptValue))
		{
			winRate += nextOptValue*WIN_RATE_DISCOUNT;
			DEBUG_FORMAT("win rate is %lf", winRate);
			if (winRate > maxWinrate)
			{
				maxWinrate = winRate;
				optStatus.clear();
				optStatus.push_back(nextStatus);
			}
			else if (winRate == maxWinrate)
			{
				optStatus.push_back(nextStatus);
			}
		}
	}
	DEBUG_FORMAT("max win rate is %lf", maxWinrate);
	DEBUG_FORMAT("size of optimal status is %d", optStatus.size());

	if (optStatus.empty())
	{
		return NULL;
	}
	if (optStatus.size() == 1)
	{
		return optStatus.front();
	}
	else
	{
		return optStatus.at(optStatus.size() / 2);
	}
}

bool ChessTree::ComputeOptimalValue(const ChessStatus* curStatus, const int curDepth, double& optimalValue)
{
	if (curDepth >= SEARCH_NEXT_DEPTH) return false;

	//Compute win rate of each next step and compute the next next step further.
	//Then combine a win rate sequence to an accumulated win rate.

	double maxWinrate = -1;
	auto allNext = curStatus->GetAllNextStatus();
	if (allNext.empty())
	{
		optimalValue = DEFAULT_WINRATE* pow(WIN_RATE_DISCOUNT, curDepth - 1);
		return true;
	}

	for (auto next : allNext)
	{
		ChessStatus* nextStatus = dynamic_cast<ChessStatus*>(next);
		double winRate;
		if (curStatus->GetWinRate(nextStatus, winRate))
		{
			double nextOptimalValue;
			if (ComputeOptimalValue(nextStatus, curDepth + 1, nextOptimalValue))
			{
				winRate += nextOptimalValue*WIN_RATE_DISCOUNT;
				if (winRate > maxWinrate)
				{
					maxWinrate = winRate;
				}
			}
			else
			{
				//Reach the end of the search.
				if (winRate > maxWinrate)
				{
					maxWinrate = winRate;
				}
			}
		}
		else
		{
			throw runtime_error("Fail to get next status.");
		}
	}

	optimalValue = maxWinrate;
	return true;
}
